/*******************************************************************************************
 * Copyright (c) 2006-2009 Alex Robson, Rob Simmons, Craig Israel, and Rachel Twyford
 *
 * Use of this software in any form requires that you have both 
 * read and agreed to the following terms:
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the "Software"), 
 * to deal in the Software without restriction, including without limitation 
 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions: 
 *
 * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright notice, 
 * this list of conditions and the following disclaimer in the 
 * documentation and/or other materials provided with the distribution.
 * Neither the name of Nvigorate nor the names of its contributors may 
 * be used to endorse or promote products derived from this software without 
 * specific prior written permission. 
 *
 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
 * DISCLAIMED. IN NO EVENT SHALL ANY CONTRIBUTOR BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * ****************************************************************************************/

using System;
using System.Collections;
using System.Reflection;
using Nvigorate.Common.Reflection;
using Nvigorate.Logging;
using Nvigorate.Relational.Repository;
using Nvigorate.Relational.State;
using PostSharp.Extensibility;
using PostSharp.Laos;

namespace Nvigorate.Aspect
{
    [MulticastAttributeUsage(MulticastTargets.Class)]
    public class DatabaseLazyLoadAspect : CompoundAspect
    {
        protected Type _lazyLoaderType = typeof(DatabaseLazyLoader<,>);
        protected string _repositoryName;
        protected bool _saveResultToObject = true;

        public string RepositoryName
        {
            get { return _repositoryName; }
            set { _repositoryName = value; }
        }
        
        public bool SaveResultToObject
        {
            get { return _saveResultToObject; }
            set { _saveResultToObject = value; }
        }

        public override void ProvideAspects(object element, LaosReflectionAspectCollection collection)
        {
            Type targetType = element as Type;

            if (_lazyLoaderType != null && _repositoryName != null)
                AddLazyLoadingAdvice(collection, targetType);
        }

        protected void AddLazyLoadingAdvice(LaosReflectionAspectCollection collection, Type targetType)
        {
            var properties = QueryBuilderHelper.GetChildPropertiesForLazyLoad(targetType);

            var lazyLoadChildAdvice = new OnInstanceMemberGet(_lazyLoaderType, _repositoryName, _saveResultToObject);
            var lazyLoadChildrenAdvice = new OnCollectionMemberGet(_lazyLoaderType, _repositoryName, _saveResultToObject);

            foreach (var property in properties)
            {
                MethodInfo setter = property.GetGetMethod();

                if (setter != null)
                {
                    if (!setter.IsStatic)
                    {
                        //Writes the property and class names that the tracking aspect is being applied to to the console
                        //during build to make it simpler to determine which properties are being tracked.
                        Console.WriteLine("Applying Lazy Loading Aspect to property {0} on type {1}", property.Name, targetType.FullName);
                        var aspect = Reflector.IsEnumerable(property.PropertyType) ? (ILaosMethodLevelAspect)
                                                                                     lazyLoadChildrenAdvice : lazyLoadChildAdvice;
                        aspect.RuntimeInitialize(setter);
                        collection.AddAspect(setter,
                                             aspect);
                    }
                }
            }
        }

        [Serializable]
        public class OnCollectionMemberGet : OnMethodBoundaryAspect
        {
            private string repositoryName;
            private string loaderTypeName;
            private bool cache = false;

            public override void OnSuccess(MethodExecutionEventArgs eventArgs)
            {
                if (LazyLoadingProxy.DisableLazyLoading)
                {
                    base.OnEntry(eventArgs);
                    return;
                }

                try
                {
                    //Gets the persistance properties. This should *never* be null
                    ITrackState state = eventArgs.Instance as ITrackState;

                    //If the object is in the process of deserialization or by some
                    //freak chance doesn't implement IPerstable, just set the value
                    //and exit
                    if (state == null)
                    {
                        base.OnEntry(eventArgs);
                        return;
                    }
                    if (state.Deserializing)
                    {
                        base.OnEntry(eventArgs);
                        return;
                    }

                    //Get the original value
                    string propertyName = eventArgs.Method.Name.Replace("get_", "");

                    var original = (IEnumerable)eventArgs.ReturnValue;
                    var collection = eventArgs.ReturnValue as ICollection;
                    //If the original value is null, we still need to set the dirty flag
                    //and add the property as changed but we have to be careful NOT
                    //to do anything else against the null
                    if (original == null || (collection != null && collection.Count == 0))
                    {
                        var parentType = state.GetType();
                        var propertyType = Reflector.GetGenericTypeArgument(parentType, propertyName);
                        var proxy = new LazyLoadingProxy(Reflector.GetType(loaderTypeName), repositoryName, parentType, propertyType);
                        var children = proxy.GetChildren(state);
                        if (cache)
                            Reflector.Write(eventArgs.Instance, propertyName, children);
                        eventArgs.ReturnValue = children;
                    }
                }
                catch (Exception ex)
                {
                    LogManager.LogError(ex, Severities.Moderate, "Lazy Load Aspect Error on {0} property {1}",
                                        eventArgs.Instance.GetType().FullName, eventArgs.Method.Name);
                }
                finally
                {
                    base.OnEntry(eventArgs);
                }
            }

            public OnCollectionMemberGet()
            {
            }

            public OnCollectionMemberGet(Type loaderType, string repositoryName, bool cache)
            {
                this.loaderTypeName = loaderType.AssemblyQualifiedName;
                this.repositoryName = repositoryName;
                this.cache = cache;
            }
        }

        [Serializable]
        internal class OnInstanceMemberGet : OnMethodBoundaryAspect
        {
            private string repositoryName;
            private string loaderTypeName;
            private bool cache = false;

            public override void OnSuccess(MethodExecutionEventArgs eventArgs)
            {
                if(LazyLoadingProxy.DisableLazyLoading)
                {
                    base.OnEntry(eventArgs);
                    return;
                }

                try
                {
                    //Gets the persistance properties. This should *never* be null
                    ITrackState tracked = eventArgs.Instance as ITrackState;

                    //If the object is in the process of deserialization or by some
                    //freak chance doesn't implement IPerstable, just set the value
                    //and exit
                    if (tracked == null)
                    {
                        base.OnEntry(eventArgs);
                        return;
                    }
                    if (tracked.Deserializing)
                    {
                        base.OnEntry(eventArgs);
                        return;
                    }

                    // Get the original value and property name
                    string propertyName = eventArgs.Method.Name.Replace("get_", "");
                    var original = eventArgs.ReturnValue;

                    // If the original value is null, then we'll call the lazy loading proxy
                    // and manipulate the return value
                    if (original == null)
                    {
                        var parentType = tracked.GetType();
                        var propertyType = Reflector.GetGenericTypeArgument(parentType, propertyName);
                        var proxy = new LazyLoadingProxy(Reflector.GetType(loaderTypeName), repositoryName, parentType, propertyType);
                        var child = proxy.GetChild(tracked);

                        // if caching is turned on, we also want to back-fill the property
                        if (cache)
                            Reflector.Write(eventArgs.Instance, propertyName, child);
                        eventArgs.ReturnValue = child;
                    }
                }
                catch (Exception ex)
                {
                    LogManager.LogError(ex, Severities.Moderate, "Lazy Load Aspect Error on {0} property {1}",
                                        eventArgs.Instance.GetType().FullName, eventArgs.Method.Name);
                }
                finally
                {
                    base.OnEntry(eventArgs);
                }
            }

            public OnInstanceMemberGet(Type loaderType, string repositoryName, bool cache)
            {
                this.loaderTypeName = loaderType.AssemblyQualifiedName;
                this.repositoryName = repositoryName;
                this.cache = cache;
            }
        }

        public DatabaseLazyLoadAspect()
        {
            
        }
    }
}